﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace XDL.Framework.Graphics
{
    /// <summary>
    /// Plasma texture generator
    /// </summary>
    class PlasmaTexture:
        Texture2D
    {
        Random m_rnd = new Random();

        public PlasmaTexture(GraphicsDevice graphicsDevice, int w, int h)
            :
            base(graphicsDevice, w, h, true, SurfaceFormat.Color)
        {
        }

        private void SetPlasmaValue(double[] plasma, int x, int y, double value)
        {
            int i = y * (this.Width+1) + x;
            if ( plasma[i] == 0 )
                plasma[i] = value;
        }
        private double GetPlasmaValue(double[] plasma, int x, int y)
        {
            return plasma[y * (this.Width+1) + x];
        }

        /// <summary>
        /// Generates the texture.
        /// </summary>
        public void Fill()
        {
            Fill(false);
        }


        public void Fill(bool wrap)
        {
            // note: width and height must be even
            double[] plasma = new double[(this.Height + 1) * (this.Width + 1)];
            if (wrap)
            {
                double startValue = m_rnd.NextDouble();
                SetPlasmaValue(plasma, 0, 0, startValue);
                SetPlasmaValue(plasma, this.Width, 0, startValue);
                SetPlasmaValue(plasma, this.Width, this.Height, startValue);
                SetPlasmaValue(plasma, 0, this.Height, startValue);
            }
            else
            {
                SetPlasmaValue(plasma, 0, 0, m_rnd.NextDouble());
                SetPlasmaValue(plasma, this.Width, 0, m_rnd.NextDouble());
                SetPlasmaValue(plasma, this.Width, this.Height, m_rnd.NextDouble());
                SetPlasmaValue(plasma, 0, this.Height, m_rnd.NextDouble());
            }
            // fill the plasma map
            Fill(plasma,0, 0, this.Width, this.Height,1);

            // now, fill the texture
            Color[] colors = new Color[this.Width * this.Height];
            for (int i = 0; i < colors.Length; i++)
            {
                int x = i % this.Height;
                int y = i / this.Width;
                colors[i] = ConvertNoiseToColor(GetPlasmaValue(plasma,x,y), x, y);
            }

            // copy the colors
            this.SetData<Color>(colors);
        }

        /// <summary>
        /// Recursive fill function
        /// </summary>
        /// <param name="plasma"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="w"></param>
        /// <param name="h"></param>
        private void Fill(double[] plasma,int x, int y, int w, int h, int level)
        {
            if (w == 0 ||
                 h == 0)
                return;

            double midAvg =
                GetPlasmaValue(plasma, x, y) +
                GetPlasmaValue(plasma, x + w, y) +
                GetPlasmaValue(plasma, x + w, y + h) +
                GetPlasmaValue(plasma, x, y + h);
            midAvg /= 4;
            midAvg += (1 - 2*m_rnd.NextDouble()) / level;

            // midAvg += m_rnd.NextDouble();

            double topAvg =
                GetPlasmaValue(plasma, x, y) +
                GetPlasmaValue(plasma, x + w, y) +
                midAvg;
            topAvg /= 3;
            double rightAvg =
                GetPlasmaValue(plasma, x + w, y) +
                GetPlasmaValue(plasma, x + w, y + h) +
                midAvg;
            rightAvg /= 3;
            double bottomAvg =
                GetPlasmaValue(plasma, x + w, y + h) +
                GetPlasmaValue(plasma, x, y + h) +
                midAvg;
            bottomAvg /= 3;
            double leftAvg =
                GetPlasmaValue(plasma, x, y + h) +
                GetPlasmaValue(plasma, x, y) +
                midAvg;
            leftAvg /= 3;

            int halfWidth = w / 2;
            int halfHeight = h / 2;
            // set the middle value
            SetPlasmaValue(plasma, x + halfHeight, y, topAvg);
            SetPlasmaValue(plasma, x + w, y + halfHeight, rightAvg);
            SetPlasmaValue(plasma, x + halfHeight, y + h, bottomAvg);
            SetPlasmaValue(plasma, x, y + halfHeight, leftAvg);
            SetPlasmaValue(plasma, x + halfHeight, y + halfHeight, midAvg);

            // subdivide
            Fill(plasma, x, y, halfWidth, halfHeight, level + 1);
            Fill(plasma, x + halfWidth, y, halfWidth, halfHeight, level + 1);
            Fill(plasma, x + halfWidth, y + halfHeight, halfWidth, halfHeight, level + 1);
            Fill(plasma, x, y + halfHeight, halfWidth, halfHeight, level + 1);
        }

        /// <summary>
        /// Convert the noise value into a color.
        /// </summary>
        /// <param name="noiseValue">The plasma noise value.</param>
        /// <param name="x">X (in texture pixel space)</param>
        /// <param name="y">Y (in texture pixel space)</param>
        /// <returns>A color.</returns>
        protected virtual Color ConvertNoiseToColor(double noiseValue, int x, int y)
        {
            byte noiseColor = (byte)(noiseValue * byte.MaxValue);
            return new Color(noiseColor, noiseColor, noiseColor);
        }
    }
}
